#include "mqtt.h"
#include "print.h"
#include "define.h"
#include "deal.h"

//////////////////////////////////////////////////////////////////////////////////////////////////

void(*restart)(void) = 0;

//////////////////////////////////////////////////////////////////////////////////////////////////

uint32_t MqttClient::pubTick = 60000;   // 60s同步一次
const char* MqttClient::c2sHTopic = "/arduino/ctos/";
const char* MqttClient::s2cHTopic = "/arduino/stoc/";

//////////////////////////////////////////////////////////////////////////////////////////////////

MqttClient::MqttClient()
    : client(nullptr)
    , choose(-1)
    , updtick(0)
    , failCnt(0)
    , pubString("")
    , syncState(true)
    , subSuccess(false) {
    for (uint16_t i = 0; i < g_portcfg_cnt; i++) {
        auto& cfg = g_portcfg[i];
        pinmap[cfg.name] = &cfg;
    }
}

MqttClient::~MqttClient() {
    if (client != nullptr) {
        if (client->connected() == true) {
            client->disconnect();
        }
        delete client;
        client = nullptr;
    }
}

void MqttClient::setup() {
    Serial.begin(9600);

    for (uint16_t i = 0; i < g_portcfg_cnt; i++) {
        auto& cfg = g_portcfg[i];
        pinMode(cfg.port, OUTPUT);
    }

    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(100);
}

void MqttClient::loop() {
    while (choose < 0 || choose >= g_servercfg_cnt) {
        scan();
        if (choose < 0 || choose >= g_servercfg_cnt) {
            failCnt++;
            if (failCnt > 10) {
                restart();
            }
            delay(100);
        } else {
            failCnt = 0;
        }
    }

    if (client == nullptr) {
        auto& scfg = g_servercfg[choose];
        println("connect to server: ", scfg.server, ", port: ", scfg.port, ", ssid: ", scfg.ssid, ", password: ", scfg.passwd);

        WiFi.begin(scfg.ssid, scfg.passwd);
        while (WiFi.status() != WL_CONNECTED) {
            delay(500);
            Serial.print(".");
        }

        smac = WiFi.macAddress();
        smac.replace(':', '-');

        c2sTopic = c2sHTopic + smac;
        s2cTopic = s2cHTopic + smac;

        sip = "";
        auto lip = WiFi.localIP();
        for (auto i = 0; i < 4; i++) {
            sip += lip[i];
            if (i != 3) {
                sip += '.';
            }
        }

        println("wifi connected, ip address: ", sip, ", mac: ", smac);

        if (client == nullptr) {
            client = new PubSubClient(espClient);
        }

        client->setServer(scfg.server, scfg.port);
        client->setCallback([this](char* topic, uint8_t* payload, unsigned int length) {
            callback(topic, payload, length);
        });
    }

    if (client->connected() == false) {
        connect();
    }
    client->loop();

    if (client->connected() == true) {
        if (subSuccess == false) {
            subSuccess = client->subscribe(s2cTopic.c_str(), 1);
            println("subscribe topic: ", s2cTopic, ", success: ", (subSuccess ? true : false));
        }
        auto now = millis();
        if (now - updtick > pubTick) {
            updtick = now;
            syncPublish();
        }
    }
}

void MqttClient::syncPublish(bool show) {
    if (client != nullptr) {
        pubString = smac + "|" + sip + "|" + (syncState ? 1 : 0) + "|[";
        for (uint16_t i = 0; i < g_portcfg_cnt; i++) {
            auto& cfg = g_portcfg[i];
            pubString = pubString + cfg.name + ":" + (cfg.onoff ? 1 : 0) + ",";
        }
        pubString += "]";

        client->publish(c2sTopic.c_str(), pubString.c_str());

        if (show == true) {
            println("publish, key: ", c2sTopic, ", data: ", pubString);
        }
    }
}

void MqttClient::scan() {
    std::set<std::string> ssidst;
    println("scan wifi ......");
    auto cnt = WiFi.scanNetworks();
    for (auto i = 0; i < cnt; i++) {
        auto ssid = WiFi.SSID(i);
        if (ssid.length() <= 0) {
            continue;
        }
        ssidst.insert(ssid.c_str());
        println(i + 1, ": ", ssid, " (", WiFi.RSSI(i), ") ", WiFi.channel(), " ", (WiFi.encryptionType(i) == ENC_TYPE_NONE ? " " : "*"));
    }

    choose = -1;
    for (auto i = 0; i < g_servercfg_cnt; i++) {
        auto& scfg = g_servercfg[i];
        if (ssidst.count(scfg.ssid) <= 0) {
            continue;
        }
        choose = i;
    }
}

void MqttClient::connect() {
    while (client->connected() == false) {
        if (client->connect(smac.c_str())) {
            subSuccess = client->subscribe(s2cTopic.c_str(), 1);
            println("mqtt connection connected, subscribe topic: ", s2cTopic, ", success: ", (subSuccess ? true : false));
            syncPublish(true);
            break;
        } else {
            println("mqtt connection failed, rc=", client->state(), ", try again in 5 seconds");
            delay(5000);
        }
    }
}

void MqttClient::callback(char* topic, uint8_t* payload, unsigned int length) {
    auto slen = strlen((const char*)payload);
    if (length <= 0) {
        length = slen;
    } else if (slen > length) {
        *(payload + length) = 0;
    }
    dealCallBack(*this, topic, (const char*)payload, length);
}